#include <linux/dcache.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pagemap.h>
#include <linux/slab.h>
#include <linux/version.h>

/******************************************************************************/
/*                                    Debug                                   */
/******************************************************************************/
#define print_error(...)	pr_err("ERROR: " __VA_ARGS__)
#define print_failed_exit()	pr_err_once("FAILED:%s()", __func__)
/* LOG */
#define print_start()		pr_debug("-->%s()", __func__)
#define print_exit()		pr_debug("<--%s()", __func__);
#define print_log(...)		pr_debug("LOG: " __VA_ARGS__);
/******************************************************************************/
/*                                File System                                 */
/******************************************************************************/
#define	TRIFS_MAGIC		439743
#define MAX_DEPTH		5
static struct file_system_type trifs;

static const char *yes = "yes";
static const char *no = "no";
static const char *dunno = "dunno";

static const struct super_operations trifs_ops;
static const struct inode_operations trifs_ino_ops;
static const struct file_operations trifs_file_ops;

/*
 * Create simple inode, calculate depth
 */
static struct inode *trifs_get_inode(struct super_block *sb,
				     const struct inode *dir,
				     umode_t mode, dev_t dev)
{
	struct inode *inode = NULL;
	int *depth = NULL;

	inode = new_inode(sb);
	depth = kmalloc(sizeof(int), GFP_KERNEL);
	if (!inode || !depth)
		return ERR_PTR(ENOMEM);
	*depth = 0;
	inode_init_owner(inode, dir, mode);
	inode->i_ino = get_next_ino();
	if (dir && dir->i_private)
		*depth = *((int *)dir->i_private) + 1;
	inode->i_private = depth;
	inode->i_op = &trifs_ino_ops;
	inode->i_fop = &trifs_file_ops;
	inc_nlink(inode);

	return inode;
}

/*
 * Clear inode
 * We need this operation to free allocated data
 */
static void trifs_evict_inode(struct inode *ino)
{
	clear_inode(ino);
	kfree(ino->i_private);
	return;
}

/*
 * mount_nodev() requires superblock initializer
 */
static int trifs_fill_super(struct super_block *sb, void *data, int silent)
{
	struct inode *root = NULL;

	/* Fill superblock */
	sb->s_maxbytes		= MAX_LFS_FILESIZE;
	sb->s_blocksize		= PAGE_CACHE_SIZE;
	sb->s_blocksize_bits	= PAGE_CACHE_SHIFT;
	sb->s_op		= &trifs_ops;
	sb->s_magic		= TRIFS_MAGIC;

	/* Root dentry */
	root = trifs_get_inode(sb, NULL, S_IFDIR | 0555, 0);
	if (!root)
		return -ENOMEM;
	sb->s_root = d_make_root(root);

	return 0;
}

static struct dentry *trifs_lookup(struct inode *dir, struct dentry *dentry,
				   unsigned int flags)
{
	struct inode *file_inode = NULL;
	print_start();

	/* We should create only this entries */
	if (strcmp(dentry->d_name.name, yes)	&&
	    strcmp(dentry->d_name.name, no)	&&
	    strcmp(dentry->d_name.name, dunno))
		return NULL;

	/* We don't need to go deeper */
	if (*((int *)dir->i_private) == MAX_DEPTH) {
		print_log("Too deep");
		return NULL;
	}

	file_inode = trifs_get_inode(dir->i_sb, dir, S_IFDIR | 0555, 0);
	if (!file_inode)
		return ERR_PTR(-ENOMEM);
	d_add(dentry, file_inode);

	print_exit();
	return NULL;
}

/*
 * Ubuntu sucks, so I can't be sure if you're on 3.10 or 3.11
 */
#if	LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)
/*
 * Here is the deal.
 * On Linux for some reason everybody ignores the ino value you put in filler.
 * For example, ls gets entries info from stat(), so you can see it's real
 * inode number.
 * Like the fella once said:
 * "Unix-based readdir implementations have historically returned a d_ino value
 * that is sometimes not equal to the stat-obtained st_ino value for that same
 * entry.
 * ...we must incur the cost of calling stat or lstat to obtain each
 * guaranteed-valid inode number."
 *
 * However it's always a good idea to protect your filesystem from stupid
 * users. I suggest two different ways we can use:
 * 1) Don't give a crap.
 *  Just put any random value in filler and wish it's never gonna chash. I made
 *  use of it for kernel >=3.11. You can try my test_app, which only prints
 *  inode number and name of every single entry, and see, that they would be as
 *  set below.
 * 2) Play as nice guy.
 *  We can create inodes in f_op->readdir(), but then put them into dcache, so
 *  lookup doesn't need to create another one. This way I used for kernel 3.10.
 *
 * So here's no magic. How sad...
 */
static int trifs_dentry_get_ino(struct dentry *parent, const char *name,
				unsigned long *ino)
{
	struct dentry *res = NULL;
	struct dentry *dentry = NULL;
	struct qstr str;
	int err = 0;
	print_start();

	*ino = 0;
	str.name = name;
	str.len = strlen(name);
	str.hash = full_name_hash(str.name, str.len);

	/* Check if entry already created */
	dentry = d_lookup(parent, &str);
	if (unlikely(!dentry)) {
		/* Allocate new dentry */
		dentry = d_alloc(parent, &str);
		if (unlikely(!dentry))
			return -ENOMEM;
		/* Now lookup allocated dentry */
		res = parent->d_inode->i_op->lookup(parent->d_inode, dentry, 0);
		if (unlikely(!dentry->d_inode)) {
			if (unlikely(res))
				err = PTR_ERR(res);
			goto out;
		}
	}
	/* Finally we got the inode value */
	*ino = dentry->d_inode->i_ino;
out:
	/* Release dentry */
	dput(dentry);

	print_exit();
	return err;
}

static int trifs_readdir(struct file *file, void *dirent, filldir_t filldir)
{
	int err = 0;
	unsigned long ino = 0;
	print_start();

	switch (file->f_pos) {
	case 0:
		filldir(dirent, ".", 1, file->f_pos++, file->f_inode->i_ino,
			DT_DIR);
	case 1:
		filldir(dirent, "..", 2, file->f_pos++,
			file->f_dentry->d_parent->d_inode->i_ino, DT_DIR);
	case 2:
		err = trifs_dentry_get_ino(file->f_dentry, yes, &ino);
		if (!ino)
			return err;
		filldir(dirent, yes, 3, file->f_pos++, ino, DT_DIR);
	case 3:
		err = trifs_dentry_get_ino(file->f_dentry, no, &ino);
		if (!ino)
			return err;
		filldir(dirent, no, 2, file->f_pos++, ino, DT_DIR);
	case 4:
		err = trifs_dentry_get_ino(file->f_dentry, dunno, &ino);
		if (!ino)
			return err;
		filldir(dirent, dunno, 5, file->f_pos++, ino, DT_DIR);
		break;
	}

	print_exit();
	return 0; /* Nobody cares what we return here */
}
#else /* LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0) */
static int trifs_iterate(struct file *file, struct dir_context *ctx)
{
	print_start();

	if ((file->f_inode->i_private) &&
	    (*((int *)file->f_inode->i_private) == MAX_DEPTH))
		return dir_emit_dots(file, ctx);

	switch (ctx->pos) {
	case 0:
	case 1:
		return dir_emit_dots(file, ctx);
	case 2:
		dir_emit(ctx, "yes", 3, 1, DT_DIR);
		ctx->pos++;
		break;
	case 3:
		dir_emit(ctx, "no", 2, 2, DT_DIR);
		ctx->pos++;
		break;
	case 4:
		dir_emit(ctx, "dunno", 5, 3, DT_DIR);
		ctx->pos++;
		break;
	default:
		break;
	}

	print_exit();
	return 0;
}
#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0) */

/*
 * We don't need any special named device to deal with fs.
 * So it just calls mount_nodev().
 */
static struct dentry *trifs_mount(struct file_system_type *fs_type, int flags,
		const char *dev_name, void *data)
{
	return mount_nodev(fs_type, flags, data, trifs_fill_super);
}

/*
 * Call kill_anon_super() to destroy everything superblock contains
 */
static void trifs_kill_sb(struct super_block *sb)
{
	return kill_anon_super(sb);
}

static int __init trifs_init(void)
{
	int err = 0;
	print_start();

	err = register_filesystem(&trifs);
	if (err)
		return err;

	print_exit();
	return 0;
}

static void __exit trifs_exit(void)
{
	print_start();

	unregister_filesystem(&trifs);

	print_exit();
	return;
}

static struct file_system_type trifs = {
	.owner		= THIS_MODULE,
	.name		= "trifs",
	.fs_flags	= FS_USERNS_MOUNT,
	.mount		= trifs_mount,
	.kill_sb	= trifs_kill_sb,
};

static const struct super_operations trifs_ops = {
	.statfs		= simple_statfs,
	.evict_inode	= trifs_evict_inode,
};

static const struct inode_operations trifs_ino_ops = {
	.lookup		= trifs_lookup,
};

static const struct file_operations trifs_file_ops = {
	.read		= generic_read_dir,
#if	LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0)
	.iterate	= trifs_iterate,
#else
	.readdir	= trifs_readdir,
#endif
};

module_init(trifs_init);
module_exit(trifs_exit);

/******************************************************************************/
/*                                Module info                                 */
/******************************************************************************/
MODULE_AUTHOR("Petr Nechaev <petr@nechaev.us>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Ternary file system");
MODULE_VERSION("1.3.3");
